package org.monkey.common.constant.util;


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.monkey.common.constant.entity.ErrorResultEnum;
import org.monkey.common.constant.entity.UrlEntity;
import org.monkey.common.constant.exception.BaseException;

/**
 * http请求工具类
 *
 * @author LiTing
 * @date 2021/3/26 16:39
 */
public class HttpClientUtil {
  private static final String DEFAULT_CHARSET = "UTF-8";
  private static volatile CloseableHttpClient httpClient = null;

  /**
   * 默认的字节读取大小
   */
  private static final int SIZE = 4 * 1024;


  public static void httpResponseToFile(CloseableHttpResponse httpResponse, String path)
      throws IOException {
    HttpEntity httpEntity = httpResponse.getEntity();
    InputStream inStream = httpEntity.getContent();
    IoUtil.inputStream2File(inStream, path, true);
  }

  /**
   * 发送Get请求
   *
   * @param url
   * @param header
   * @return T
   * @throws
   * @version V1.0.0
   * @date 2021/3/26 17:06
   */
  public static String doGet(String url, Map<String, String> header) {
    CloseableHttpClient httpClient = getHttpClient();
    HttpGet get = new HttpGet(getURI(url));
    setHeader(get, header);
    return getResult(httpClient, get);
  }

  /**
   * 发送Get请求
   *
   * @param url
   * @param header
   * @return T
   * @throws
   * @version V1.0.0
   * @date 2021/3/26 17:06
   */
  public static String doGetWithProxy(
      String url, Map<String, String> header, String ip, int port) {
    CloseableHttpClient httpClient = getHttpClient();
    HttpGet get = new HttpGet(getURI(url));
    RequestConfig c = get.getConfig();
    if (c == null) {
      c = RequestConfig.DEFAULT;
    }
    RequestConfig requestConfig = RequestConfig.copy(c).setProxy(new HttpHost(ip, port)).build();
    get.setConfig(requestConfig);
    setHeader(get, header);
    return getResult(httpClient, get);
  }

  /**
   * 发送get请求 带参
   *
   * @param url
   * @param params
   * @param header
   * @return T
   * @throws
   * @version V1.0.0
   * @date 2021/3/26 17:06
   */
  public static String doGet(
      String url, Map<String, Object> params, Map<String, String> header) {
    CloseableHttpClient httpClient = getHttpClient();
    HttpGet get = new HttpGet(getURI(dealUrl(url, params)));
    setHeader(get, header);
    return getResult(httpClient, get);
  }

  /**
   * 下载文件
   *
   * @param url
   * @param header
   * @param filePath
   * @return void
   * @throws
   * @version V1.0.0
   * @date 2021/3/26 17:06
   */
  public static void doGetFile(String url, Map<String, String> header, String filePath) {
    CloseableHttpClient httpClient = getHttpClient();
    HttpGet get = new HttpGet(getURI(url));
    setHeader(get, header);
    try (CloseableHttpResponse res = doExecute(httpClient, get)) {
      httpResponseToFile(res, filePath);
    } catch (IOException e) {
      throw new BaseException(ErrorResultEnum.SYSTEM_ERROR);
    }finally{
      get.releaseConnection();
    }
  }

  /**
   * 获取文件输入流
   *
   * @param url
   * @param header
   * @return java.io.InputStream
   * @throws
   * @version V1.0.0
   * @date 2021/3/26 17:05
   */
  public static InputStream doGetFileInputStream(String url, Map<String, String> header) {
    CloseableHttpClient httpClient = getHttpClient();
    HttpGet get = new HttpGet(getURI(url));
    setHeader(get, header);
    try (CloseableHttpResponse res = doExecute(httpClient, get)) {
      return res.getEntity().getContent();
    } catch (IOException e) {
      e.printStackTrace();
      throw new BaseException(ErrorResultEnum.SYSTEM_ERROR);
    }finally{
      get.releaseConnection();
    }
  }

  /**
   * 发送 application/x-www-form-urlencoded请求
   *
   * @param url
   * @param param
   * @param header
   * @return T
   * @throws
   * @version V1.0.0
   * @date 2021/3/26 17:05
   */
  public static String doPost(
      String url, Map<String, Object> param, Map<String, String> header) {
    CloseableHttpClient httpClient = getHttpClient();
    HttpPost httpPost = new HttpPost(getURI(url));
    setHeader(httpPost, header);
    setUrlFormEntity(httpPost, param);
    return getResult(httpClient, httpPost);
  }

  /**
   * 发送application/json请求
   *
   * @param url
   * @param json
   * @param header
   * @return T
   * @throws
   * @version V1.0.0
   * @date 2021/3/26 17:04
   */
  public static String doPostJson(String url, String json, Map<String, String> header) {
    CloseableHttpClient httpClient = getHttpClient();
    HttpPost httpPost = new HttpPost(getURI(url));
    setHeader(httpPost, header);
    setJsonEntity(httpPost, json);
    return getResult(httpClient, httpPost);
  }

  /**
   * form表单文件上传
   *
   * @param url
   * @param params
   * @param headers
   * @return T
   * @throws
   * @version V1.0.0
   * @date 2021/3/26 17:23
   */
  public static String doPostFormData(
      String url, Map<String, Object> params, Map<String, String> headers) {
    CloseableHttpClient httpClient = getHttpClient();
    HttpPost httpPost = new HttpPost(getURI(url));
    setHeader(httpPost, headers);
    setFormDataEntity(httpPost, params);
    return getResult(httpClient, httpPost);
  }

  /**
   * 仅上传文件
   *
   * @param url
   * @param file
   * @param headers
   * @return T
   * @throws
   * @version V1.0.0
   * @date 2021/3/26 17:30
   */
  public static String doPostFile(
      String url, File file, Map<String, String> headers) {
    CloseableHttpClient httpClient = getHttpClient();
    HttpPost httpPost = new HttpPost(getURI(url));
    setHeader(httpPost, headers);
    ByteArrayEntity byteArrayEntity = null;
    try {
      if (file != null) {
        byteArrayEntity = new ByteArrayEntity(IoUtil.file2Byte(file));
      }
      httpPost.setEntity(byteArrayEntity);
      return getResult(httpClient, httpPost);
    } catch (IOException e) {
      e.printStackTrace();
      throw new BaseException(ErrorResultEnum.SYSTEM_ERROR);
    }
  }

  private static CloseableHttpClient getHttpClient() {
    if (Objects.isNull(httpClient)) {
      synchronized (HttpClientUtil.class) {
        if (Objects.isNull(httpClient)) {
          PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
          // 连接池总容量
          connectionManager.setMaxTotal(1000);
          // 每个host为一组，此参数用于控制每组中连接池的容量 【重要】
          connectionManager.setDefaultMaxPerRoute(30);
          RequestConfig requestConfig = RequestConfig.custom()
              // 从连接池中取连接超时时间
              .setConnectionRequestTimeout(2000)
              // 建立链接超时时间
              .setConnectTimeout(2000)
              // 等待读取数据时间
              .setSocketTimeout(10000).build();

          httpClient = HttpClientBuilder.create()
              // 关闭自动重试
              .disableAutomaticRetries()
              // 设置连接池
              .setConnectionManager(connectionManager)
              // 设置链接最大存活时间
              .setConnectionTimeToLive(2, TimeUnit.MINUTES)
              // 设置超时时间
              .setDefaultRequestConfig(requestConfig).build();
        }
      }
    }
    return httpClient;
  }


  public static void setHeader(HttpRequestBase hb, Map<String, String> headers) {
    if (headers != null) {
      for (String key : headers.keySet()) {
        hb.setHeader(key, headers.get(key));
      }
    }
  }

  private static CloseableHttpResponse doExecute(CloseableHttpClient hc, HttpRequestBase hb)
      throws IOException {

    CloseableHttpResponse ch = hc.execute(hb);
    return ch;
  }

  private static String dealUrl(String url, Map<String, Object> params) {
    UrlEntity urlEntity = UrlUtil.parse(url);
    urlEntity.addParam(params);
    return urlEntity.toUrl();
  }

  public static void setUrlFormEntity(HttpPost httpPost, Map<String, Object> param) {
    if (param != null) {
      List<NameValuePair> paramList = new ArrayList<>();
      for (String key : param.keySet()) {
        if (param.get(key) != null) {
          paramList.add(new BasicNameValuePair(key, String.valueOf(param.get(key))));
        }
      }
      UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, StandardCharsets.UTF_8);
      httpPost.setEntity(entity);
    }
  }

  public static void setJsonEntity(HttpPost httpPost, String json){
    StringEntity entity = new StringEntity(json, StandardCharsets.UTF_8);
    entity.setContentType("application/json;charset=utf-8");
    httpPost.setEntity(entity);
  }

  public static void setFormDataEntity(HttpPost httpPost, Map<String, Object> params) {
    MultipartEntityBuilder mEntityBuilder = MultipartEntityBuilder.create();
    mEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
    mEntityBuilder.setCharset(StandardCharsets.UTF_8);
    ContentType textContent = ContentType.create("text/plain", StandardCharsets.UTF_8);
    params.forEach(
        (k, v) -> {
          if (v != null) {
            if (v instanceof File) {
              File file = (File) v;
              mEntityBuilder.addBinaryBody(k, file);
            } else if (v instanceof Collection) {
              Collection cl = (Collection) v;
              cl.forEach(
                      c -> {
                        File f = (File) c;
                        mEntityBuilder.addBinaryBody(k, f);
                      });
            } else {
              mEntityBuilder.addTextBody(k, v.toString(), textContent);
            }
          }
        });
    httpPost.setEntity(mEntityBuilder.build());
  }



  public static String getResult(CloseableHttpClient hc, HttpRequestBase hb) {
    try (CloseableHttpResponse res = doExecute(hc, hb)) {
      return EntityUtils.toString(res.getEntity(), DEFAULT_CHARSET);
    } catch (IOException e) {
      throw new BaseException(ErrorResultEnum.SYSTEM_ERROR);
    }finally{
      hb.releaseConnection();
    }
  }

  public static final URI getURI(String url) {
    try {
      URL ur = new URL(url);
      URI uri = new URI(ur.getProtocol(), null, ur.getHost(), ur.getPort(), ur.getPath(), ur.getQuery(), null);
      return uri;
    } catch (Exception e) {
      throw new BaseException(ErrorResultEnum.URL_INVALID);
    }
  }

}
